談到架構設計/物件導向設計,SOLID原則就是常常會被提到的方式
但是只要符合SOLID就是好的設計嗎?
今天來看看舉兩個例子來討論SOLID的實踐
async def send_messages(self, sheet_name, websocket: WebSocket):
message_to_send_list = self.get_message_list()
status = ['status']
try:
chatwork_dict = {}
sender_list = self.get_sender_list(sheet_name)
for keyword in sender_list:
chatwork = ChatworkAPI(
Config.CHATWORK_CONFIGS.HEADER_KEY_DICT[keyword])
my_contacts = chatwork.get_contacts()
chatwork_dict[keyword] = {
'chatwork': chatwork, 'contacts': my_contacts, 'check_contacts': []}
completed_count = 0
total_to_send = len(message_to_send_list)
for index, row in message_to_send_list.iterrows():
completed_count = completed_count + 1
percentage = completed_count / total_to_send * 100
if row['from_whom_name_list'] != '':
chatwork_agent = chatwork_dict[row['from_whom_name_list']]['chatwork']
sender_contacts = chatwork_dict[row['from_whom_name_list']]['contacts']
ignore_contacts = chatwork_dict[row['from_whom_name_list']
]['check_contacts']
else:
write_back_status = 'no Sender'
status.append(write_back_status)
await websocket.send_json({'is_success': write_back_status,
'percentage': str(percentage),
'completed': {'google_sheet_index': index+1,
'to_whom_name': row['to_whom_name_list'],
'to_whom_nick_name': row['to_whom_nick_name_list'],
'status': write_back_status
}
})
continue
if chatwork_agent.remaining_limit > 0:
write_back_status = row['status']
friend = self.get_room_id(
row['to_whom_id_list'], sender_contacts)
if friend and write_back_status != 'success' and row['message_content'] != '':
message_content = "".join((
'[To:', row['to_whom_id_list'], '] ', friend['name'].replace('#', '-'), '\n', row['message_content']))
message_content = message_content.replace(
'[XXX]', row['to_whom_nick_name_list'])
replace_col = list(message_to_send_list.columns)[4:-2]
for keyword in replace_col:
message_content = message_content.replace(
keyword, row[keyword])
room_id = friend['room_id']
# etc 省略兩百行
當時非常菜的我收到這個MR氣急敗壞的把同事請過來問
「你知道一個function只應該做一件事嗎?這個function在做什麼?」
得到了「送訊息啊」的回答,我啞口無言,確實只幹了一件「大事」
這是對責任的粒度拆解的問題,通常不熟悉商業邏輯或是不熟悉應用場景就會寫出這樣的東西
其實這裡面大概就包含以下幾種責任
參數檢查
結果檢查
組API
回傳值處理
錯誤處理
取得聯絡清單並處理結果
etc.
所以遇到這種事情有兩種方式可以先處理
收到MR的當下請對方簡述某段程式的處理流程
並且請對方依據他簡述的流程拆分
開發前請對方進行Design Review,確保雙方對
責任的粒度、控制的粒度等SOLID層面的東西有共識
這也告訴我們在帶實習生或新人的時候絕對不能只是把SOLID守則丟給對方請他遵守
因為SOLID裡面很多執行細節,是仰賴團隊長久以來的共識的,並不是SOLID能定義的
SOLID with 商業邏輯
def split_video_into_frames(self):
video_path_list = self.list_video_files()
# Process
for vid_idx, video_path in enumerate(video_path_list):
_, filename = os.path.split(video_path)
print(f"Processing video: {video_path}")
cap = cv2.VideoCapture(video_path)
assert cap.isOpened(), "Video not found!!!"
vid_basename = os.path.basename(video_path).split('.')[0]
info = get_vid_info(cap)
frame_count = 0
fps = info['fps']
self.video_name_fps_mapping[filename] = fps
while (cap.isOpened()):
ret, frame = cap.read()
if ret == True:
# Process frames
pass
frame_count += 1
if info['frame_count'] == frame_count:
break
else:
print(f"\nINFO: {vid_idx}, {video_path}")
print("============================")
break
cap.release()
這段Code算不算是單一責任呢?裡面也做至少三件事情
所以應該要把這段程式拆成三段嗎?那如果這段程式只出現過一次呢?
其實正解是應該根據這段程式的上層 商業邏輯
如果解析影片讀取圖片的方式沒有其他可能性,所有商業邏輯都這樣切影片
那就把這堆Code放著就算了
但是如果每個專案有不同的方式,那當然要將責任分離
所以
遵守SOLID原則是設計中很基本的要件
但是只規定SOLID原則是不足的,在做這些設計的時候
還需要團隊共識跟對商業模式的理解與配合才能讓設計更符合開發者需要